package search

import (
	"log"
	"sync"
)

/**
1 这个变量没有定义在任何函数作用域内，所以会被当成包级变量。这个变量使用关键字 var声明，而且声明为 Matcher 类型的映射（map），
这个映射以 string 类型值作为键，Matcher类型值作为映射后的值,

2 Go 语言里，标识符要么从包里公开，要么不从包里公开。当代码导入了一个包时，程序可以直接访问这个包中任意一个公开的标识符。这些标识符以大写字母开头。以小写字母开头的
标识符是不公开的，不能被其他包中的代码直接访问。但是，其他包可以间接访问不公开的标识符。例如，一个函数可以返回一个未公开类型的值，那么这个函数的任何调用者，哪怕调用者不
是在这个包里声明的，都可以访问这个值。

3 行变量声明还使用赋值运算符和特殊的内置函数 make 初始化了变量

4 map 是 Go 语言里的一个引用类型，需要使用 make 来构造。如果不先构造 map 并将构造后的值赋值给变量，会在试图使用这个 map 变量时收到出错信息。
这是因为 map 变量默认的零值是 nil。

5 在 Go 语言中，所有变量都被初始化为其零值。对于数值类型，零值是 0；对于字符串类型，零值是空字符串；对于布尔类型，零值是 false；对于指针，零值是 nil。对于引用类型来说，
所引用的底层数据结构会被初始化为对应的零值。但是被声明为其零值的引用类型的变量，会返回 nil 作为其值

*/
var matchers = make(map[string]Matcher)

func Run(searchTerm string) {
	feeds, err := RetrieveFeeds()
	if err != nil {
		log.Fatalf("发生异常")
	}
	//创建一个无缓冲的通道，接受匹配后的结果
	results := make(chan *Result)
	//构造一个waitGroup,以便处理所有的数据源
	var waitGroup sync.WaitGroup

	//每个数据源的goroutine的数量
	waitGroup.Add(len(feeds))
	/*
		这是第二次看到使用了下划线标识符。第一次是在 main.go 里导入 matchers 包的时候。这
		次，下划线标识符的作用是占位符，占据了保存 range 调用返回的索引值的变量的位置。如果
		要调用的函数返回多个值，而又不需要其中的某个值，就可以使用下划线标识符将其忽略。在我
		们的例子里，我们不需要使用返回的索引值，所以就使用下划线标识符把它忽略掉。
	*/
	for _, feed := range feeds {
		//获取一个匹配器用于查找
		matcher, exists := matchers[feed.Type]
		if !exists {
			matcher = matchers["default"]
		}

		/*
			  启动一个goroutine来执行搜索

			一个 goroutine 是一个独立于其他函
			数运行的函数。使用关键字 go 启动一个 goroutine，并对这个 goroutine 做并发调度。在第 38 行，
			我们使用关键字 go 启动了一个匿名函数作为 goroutine。匿名函数是指没有明确声明名字的函
			数。在 for range 循环里，我们为每个数据源，以 goroutine 的方式启动了一个匿名函数。这
			样可以并发地独立处理每个数据源的数据。
		*/
		go func(matcher Matcher, feed *Feed) {
			Match(matcher, feed, searchTerm, results)
			//递减 WaitGroup 的计数。一旦每个goroutine 都执行调用 Match 函数和 Done 方法，程序就知道每个数据源都处理完成
			waitGroup.Done()
		}(matcher, feed)
	}
	//启动一个goroutine来监控是否所有的工作都做完了
	go func() {
		waitGroup.Wait()
		//用关闭通道的方式通知Display函数
		//退出程序
		close(results)
	}()

	// 启动函数，显示返回的结果，并且
	// 在最后一个结果显示完后返回
	Display(results)
}

// Register 调用时，会注册一个匹配器，提供给后面的程序使用
func Register(feedType string, matcher Matcher) {
	if _, exists := matchers[feedType]; exists {
		log.Fatalln(feedType, "Matcher already registered")
	}

	log.Println("Register", feedType, "matcher")
	matchers[feedType] = matcher
}
